﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;

namespace Co_location
{
    class Food
    {
        static Dictionary<string, int> wifi2newLoc;
        static Dictionary<string, int> gps2newLoc;

        static int maxWifiLoc = 1000;
        static int maxGpsLoc = 2000;

        static double[] linkgraph;

        static double timethrshld = 1;

        static int nodesize = 37;

        static int labelTypeindex = 5;
        static double KnownRatio = 0.75;
        static string[] labels = new string[] { "Calories", "Carbs", "Fat", "Protein", "Dish_combo", "high_calories", "cal_level" };

        public static void setup(int lt, double kr,double tt)
        {
            labelTypeindex = lt;
            KnownRatio = kr;
            timethrshld = tt;
        }

        public static void ReadFoodCSV()
        {
        
            string root = "D:\\Work\\Pervasive\\Food\\food\\matlab\\outputs";

            wifi2newLoc = new Dictionary<string, int>();
            gps2newLoc = new Dictionary<string, int>();

            SortedDictionary<int, List<FoodInstance>> alldata = new SortedDictionary<int,List<FoodInstance>>();
            linkgraph = new double[nodesize * nodesize];

            int count = 0;

            for (int i = 0; i < nodesize; i++)
            {
                linkgraph[i * nodesize + i] = 1;
                string filepath = root + "\\" + i + ".csv";
                StreamReader reader = new StreamReader(filepath);
                
                string line = "";
                string[] splits;

                List<FoodInstance> instances = new List<FoodInstance>();

                while ((line = reader.ReadLine()) != null)
                {
                    splits = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    int p;
                    if (int.TryParse(splits[0], out p))
                    {
                        instances.Add(new FoodInstance(splits, filepath));
                        count++;
                    }
                }
                reader.Close();

                alldata.Add(i,instances);
            }

            Console.WriteLine("Nodes : " + count);
            calcColocation(alldata);
            Console.WriteLine("Colocation Calculated");
            outputTruth(alldata);
            outputKnown(alldata);
            outputGraph(alldata);
            outputArff();
            outputMatrix();
        }

        private static void calcColocation(SortedDictionary<int, List<FoodInstance>> alldata)
        {
            SortedDictionary<int, List<FoodInstance>>.Enumerator enumOne = alldata.GetEnumerator();
            
            while (enumOne.MoveNext())
            {
                int idOne = enumOne.Current.Key;
                SortedDictionary<int, List<FoodInstance>>.Enumerator enumTwo = alldata.GetEnumerator();

                while (enumTwo.MoveNext())
                {
                    int idTwo = enumTwo.Current.Key;

                    if (idOne >= idTwo)
                        continue;

                    List<FoodInstance> instOne = enumOne.Current.Value;
                    List<FoodInstance> instTwo = enumTwo.Current.Value;
                    int Oindex = 0, Tindex = 0;

                    int colocnum = 0;

                    while (Oindex<instOne.Count && Tindex<instTwo.Count)                        
                    {
                        if (Math.Abs(instOne[Oindex].Time_start - instTwo[Tindex].Time_start) < timethrshld)
                        {
                            FoodInstance o = instOne[Oindex];
                            FoodInstance t = instTwo[Tindex];
                            bool cl = IsCoLocate(ref o, ref t);
                            instOne[Oindex] = o;
                            instTwo[Tindex] = t;

                            Oindex++;
                            Tindex++;

                            if (cl)
                                colocnum++;
                        }
                        else
                        {
                            FoodInstance o = instOne[Oindex];
                            FoodInstance t = instTwo[Tindex];
                            IsCoLocate(ref o, ref t);
                            instOne[Oindex] = o;
                            instTwo[Tindex] = t;

                            if (instOne[Oindex].Time_start < instTwo[Tindex].Time_start)
                                Oindex++;
                            else
                                Tindex++;                            
                        }
                    }

                    linkgraph[enumOne.Current.Key * nodesize + enumTwo.Current.Key] = 2 * (double)colocnum / (double)(instOne.Count + instTwo.Count);
                    linkgraph[enumTwo.Current.Key * nodesize + enumOne.Current.Key] = 2 * (double)colocnum / (double)(instOne.Count + instTwo.Count);
                }
            }
        }

        private static bool IsCoLocate(ref FoodInstance instOne,ref FoodInstance instTwo)
        {
            bool GPSCo = false, WifiCo = false, LocationCo = false;

            if (instOne.Location == instTwo.Location && instOne.Location > 0)
                LocationCo = true;
            else if (instOne.wifis == instTwo.wifis)
            {
                WifiCo = true;
            }
            else
            {
                double dist = Math.Sqrt((instOne.GPS_lat - instTwo.GPS_lat) * (instOne.GPS_lat - instTwo.GPS_lat) + (instOne.GPS_lon - instTwo.GPS_lon) * (instOne.GPS_lon - instTwo.GPS_lon));

                if (dist < 0.00015 && (instOne.GPS_lat != 0 || instOne.GPS_lon != 0 || instTwo.GPS_lon != 0 || instTwo.GPS_lat != 0))
                    GPSCo = true;
            }

            if (instOne.newLocation < 0)
            {
                if (instOne.GPS_lat == 0 && instOne.GPS_lon == 0)
                {
                    if (!wifi2newLoc.TryGetValue(instOne.wifis, out instOne.newLocation))
                        wifi2newLoc.Add(instOne.wifis, (++maxWifiLoc));

                    instOne.newLocation = wifi2newLoc[instOne.wifis];
                }
                else
                {
                    int lati = (int)Math.Floor(instOne.GPS_lat / 0.0001);
                    int loni = (int)Math.Floor(instOne.GPS_lon / 0.0001);

                    string key = lati.ToString() + loni.ToString();

                    if (!gps2newLoc.TryGetValue(key, out instOne.newLocation))
                        gps2newLoc.Add(key, (++maxGpsLoc));

                    instOne.newLocation = gps2newLoc[key];
                }                
            }

            if (instTwo.newLocation < 0)
            {
                if (WifiCo || GPSCo)
                    instTwo.newLocation = instOne.newLocation;
                else
                {
                    if (instTwo.GPS_lat == 0 && instTwo.GPS_lon == 0)
                    {
                        if (!wifi2newLoc.TryGetValue(instTwo.wifis, out instTwo.newLocation))
                            wifi2newLoc.Add(instTwo.wifis, (++maxWifiLoc));

                        instTwo.newLocation = wifi2newLoc[instTwo.wifis];
                    }
                    else
                    {
                        int lati = (int)Math.Floor(instTwo.GPS_lat / 0.0001);
                        int loni = (int)Math.Floor(instTwo.GPS_lon / 0.0001);

                        string key = lati.ToString() + loni.ToString();

                        if (!gps2newLoc.TryGetValue(key, out instTwo.newLocation))
                            gps2newLoc.Add(key, (++maxGpsLoc));

                        instTwo.newLocation = gps2newLoc[key];
                    }
                }
            }

            return GPSCo || WifiCo || LocationCo;
        }

        private static void outputArff()
        {
            string path = System.IO.Directory.GetCurrentDirectory() + "\\food-schema.arff";
            StreamWriter writer = new StreamWriter(path);

            writer.WriteLine("@nodetype Food");
            writer.WriteLine("@attribute Name KEY");
            writer.WriteLine("@attribute Share CATEGORICAL");
            writer.WriteLine("@attribute Category CATEGORICAL");
            writer.WriteLine("@attribute Since_last_meal INT");
            writer.WriteLine("@attribute newLocation INT");
            writer.WriteLine(string.Format("@attribute {0} CATEGORICAL", labels[labelTypeindex]));
            writer.WriteLine("@nodedata food-truth.csv");

            writer.WriteLine();

            writer.WriteLine("@edgetype Linked Food Food"); 
            writer.WriteLine("@Reversible");
            writer.WriteLine("@edgedata food-graph.rn");

            writer.Close();
        }

        private static void outputTruth(SortedDictionary<int, List<FoodInstance>> alldata)
        {
            string path = System.IO.Directory.GetCurrentDirectory() + "\\food-truth.csv";
            StreamWriter writer = new StreamWriter(path);            
            
            SortedDictionary<int, List<FoodInstance>>.Enumerator enumerator = alldata.GetEnumerator();
            string name;
            int instanceid = 0;
            while (enumerator.MoveNext())
            {
                int id = enumerator.Current.Key;
                List<FoodInstance>.Enumerator instances = enumerator.Current.Value.GetEnumerator();

                instanceid = 0;
                while (instances.MoveNext())
                {
                    name = Name(id, instanceid);
                    FoodInstance instance = instances.Current;

                    writer.WriteLine(name + "," + instance.Share + "," + instance.Category + "," + instance.Since_last_meal + "," + instance.newLocation + "," + instance.getLabelValue(labelTypeindex));
                    instanceid++;
                }
            }

            writer.Close();
        }

        private static void outputGraph(SortedDictionary<int, List<FoodInstance>> alldata)
        {
            string path = System.IO.Directory.GetCurrentDirectory() + "\\food-graph.rn";
            StreamWriter writer = new StreamWriter(path);
            
            /*for (int i = 0; i < nodesize; i++)
            {
                for (int j = 0; j < nodesize; j++)
                {
                    if (linkgraph[i * nodesize + j] < 0.000001)
                        writer.Write("0 ");
                    else
                        writer.Write(linkgraph[i * nodesize + j].ToString("0.00") + " ");
                }
                writer.WriteLine();
                writer.WriteLine();
            }
            writer.Close();*/

            int lines = 0;
            SortedDictionary<int, List<FoodInstance>>.Enumerator enumOne = alldata.GetEnumerator();

            while (enumOne.MoveNext())
            {
                int idOne = enumOne.Current.Key;
                SortedDictionary<int, List<FoodInstance>>.Enumerator enumTwo = alldata.GetEnumerator();

                while (enumTwo.MoveNext())
                {
                    int idTwo = enumTwo.Current.Key;

                    if (idOne > idTwo)
                        continue;

                    if (linkgraph[idOne * nodesize + idTwo] < 0.000001)
                        continue;

                    List<FoodInstance> instOnes = enumOne.Current.Value;
                    List<FoodInstance> instTwos = enumTwo.Current.Value;

                    string nameOne, nameTwo;
                    Random rand = new Random();
                    for (int i = 0; i < instOnes.Count; i++)
                        for (int j = 0; j < instTwos.Count; j++)
                        {
                            if (idOne == idTwo && i >= j)
                                continue;

                            nameOne = Name(idOne, i);
                            nameTwo = Name(idTwo, j);
                            if (rand.NextDouble() < 2)
                            {
                                writer.WriteLine(nameOne + "," + nameTwo + "," + linkgraph[idOne * nodesize + idTwo].ToString("0.000"));
                                lines++;
                            }
                        }
                }
            }
            writer.Close();
            Console.WriteLine("Edges : " + lines);
        }

        private static void outputKnown(SortedDictionary<int, List<FoodInstance>> alldata)
        {
            string path = System.IO.Directory.GetCurrentDirectory() + "\\food-known.csv";
            StreamWriter writer = new StreamWriter(path);

            SortedDictionary<int, List<FoodInstance>>.Enumerator enumerator = alldata.GetEnumerator();
            string name;
            int instanceid = 0,selected = 0;
            Random rand = new Random(DateTime.Now.Millisecond);
            while (enumerator.MoveNext())
            {
                int id = enumerator.Current.Key;
                List<FoodInstance>.Enumerator instances = enumerator.Current.Value.GetEnumerator();

                instanceid = 0;
                while (instances.MoveNext())
                {
                    name = Name(id, instanceid);
                    FoodInstance instance = instances.Current;

                    if (rand.NextDouble() <= KnownRatio)
                    {
                        writer.WriteLine(name + "," + instance.getLabelValue(labelTypeindex));
                        selected++;
                    }
                    instanceid++;
                }
            }
            writer.Close();

            Console.WriteLine("Selected Known: " + selected);
        }

        private static void outputMatrix()
        {
            string path = System.IO.Directory.GetCurrentDirectory() + "\\food-matrix.txt";
            StreamWriter writer = new StreamWriter(path);

            for (int i = 0; i < nodesize; i++)
            {
                for (int j = 0; j < nodesize; j++)
                {
                 //   if (linkgraph[i * nodesize + j] < 0.000001)
                   //     writer.Write("0 ");
                    //else
                        writer.Write(linkgraph[i * nodesize + j].ToString("0.00") + " ");
                }
                writer.WriteLine();
                writer.WriteLine();
            }
            writer.Close();
        }
        
        private static string Name(int userID, int foodID)
        {
            return userID + "F" + foodID;
        }
    }

    struct FoodInstance
    {
        //--------features:----------

        public int Time_start;
        public int Duration;
        public int Share;
        public int Category;
        public double GPS_lat;
        public double GPS_lon;
        public int Since_last_meal;
        public int Eat_there;
        public string wifis;
        public int Location;

        //---labels:--------
        
        public int Calories;
        public int Carbs;
        public int Fat;
        public int Protein;
        public int Dish_combo;
        public int high_calories;
        public int cal_level;

        //---newLocation:-------
        public int newLocation;

        public FoodInstance(string[] splits, string filepath)
        {
            if (splits.Length != 17 && splits.Length != 15)
                throw new Exception(" Length not match: " + splits.Length + "\n from: " + filepath);

            try
            {
                Time_start = int.Parse(splits[0]);
                Duration = int.Parse(splits[1]);
                Share = int.Parse(splits[2]);

                if (!int.TryParse(splits[3], out Category))
                {
                    switch (splits[3])
                    {
                        case "Breakfast":
                            Category = 1;
                            break;
                        case "Lunch":
                            Category = 2;
                            break;
                        case "Supper":
                            Category = 3;
                            break;
                        case "NightSnack":
                            Category = 4;
                            break;
                        default:
                            throw new Exception("Unknown Category Type: " + splits[3]);
                    }
                }
                GPS_lat = double.Parse(splits[4]);
                GPS_lon = double.Parse(splits[5]);
                Since_last_meal = int.Parse(splits[6]);
                Eat_there = int.Parse(splits[7]);
                wifis = splits[8];
                Location = int.Parse(splits[9]);

                Calories = int.Parse(splits[10]);
                Carbs = int.Parse(splits[11]);
                Fat = int.Parse(splits[12]);
                Protein = int.Parse(splits[13]);
                Dish_combo = int.Parse(splits[14]);

                if (splits.Length == 15)
                {
                    high_calories = (Calories >= 300) ? 1 : 0;

                    if (Calories < 200)
                        cal_level = 1;
                    else if (Calories < 300)
                        cal_level = 2;
                    else if (Calories < 400)
                        cal_level = 3;
                    else
                        cal_level = 4;
                }
                else
                {
                    high_calories = int.Parse(splits[15]);
                    cal_level = int.Parse(splits[16]);
                }

                if (Location > 0)
                    newLocation = Location;
                else
                    newLocation = -10;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                throw new Exception(filepath);               
            }
        }

        public int getLabelValue(int labelTypeIndex)
        {
            switch (labelTypeIndex)
            {
                case 0:
                    return Calories;
                case 1:
                    return Carbs;
                case 2:
                    return Fat;
                case 3:
                    return Protein;
                case 4:
                    return Dish_combo;
                case 5:
                    return high_calories;
                case 6:
                    return cal_level;
                default:
                    return -1;
            }            
        }
    }
}